﻿using System;
using System.ComponentModel;
using System.Linq;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// Adapts a <see cref="Model.Elements.CWNotation.StaffConnector"/> instance to a Windows Presentation Foundation view.
    /// </summary>
    public abstract class StaffConnector : ViewModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="StaffConnector"/> instance.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="System"/>
        /// </para>
        /// <para>
        /// The element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="Model.Elements.CWNotation.StaffConnector"/>
        /// </para>
        /// <para>
        /// The adapted model element instance.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedElement"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NullReferenceException">
        /// <paramref name="owner"/> is <see langword="null"/>.
        /// The values of the <see cref="ViewModelElement.UndoStack"/> and <see cref="ViewModelElement.RedoStack"/> properties
        /// are passed directly to the base class (<see cref="ViewModelElement"/>) constructor. Thus, there is not
        /// an occasion to react with a more appropriate exception.
        /// </exception>
        protected StaffConnector(System owner, Model.Elements.CWNotation.StaffConnector adaptedElement)
            : base(adaptedElement, owner, owner.UndoStack, owner.RedoStack)
        {
            this.lowerStaff = owner.Staves.First(s => s.AdaptedElement == adaptedElement.LowerStaff);
            this.upperStaff = owner.Staves.First(s => s.AdaptedElement == adaptedElement.UpperStaff);

            if (lowerStaff == null || upperStaff == null)
                throw new ArgumentException("The adaptedElement refers staves that are not a part of the owner system!");

            lowerStaff.PropertyChanged += new PropertyChangedEventHandler(Staff_PropertyChanged);
            upperStaff.PropertyChanged += new PropertyChangedEventHandler(Staff_PropertyChanged);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the property of the lower or the upper staff has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Staff_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (sender == lowerStaff)
            {
                if (e.PropertyName == "Bottom")
                {
                    RaisePropertyChanged("Bottom");
                    RaisePropertyChanged("Center");
                }
            }
            else if (sender == upperStaff)
            {
                if (e.PropertyName == "Top")
                {
                    RaisePropertyChanged("Top");
                    RaisePropertyChanged("Center");
                }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates a view-model adapter for a given staff connector element.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="System"/>
        /// </para>
        /// <para>
        /// The system that will own the created adapter.
        /// </para>
        /// </param>
        /// <param name="connector">
        /// <para>
        /// Type: <see cref="Model.Elements.CWNotation.StaffConnector"/>
        /// </para>
        /// <para>
        /// The model connector element for which the adapter will be created.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="StaffConnector"/>
        /// </para>
        /// <para>
        /// The created staff connector adapter.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="connector"/> has an unknown type.
        /// </exception>
        public static StaffConnector CreateAdapter(System owner, Model.Elements.CWNotation.StaffConnector connector)
        {
            if (connector == null)
                throw new ArgumentNullException("connector");

            if (connector is Model.Elements.CWNotation.Brace)
                return new Brace(owner, connector as Model.Elements.CWNotation.Brace);
            else
                throw new ArgumentException("Unknown connector type!", "connector");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the y-coordinate of the bottom connector point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the connector's bottom
        /// point from the top edge of the owner section.
        /// </para>
        /// </value>
        public double Bottom
        {
            get { return lowerStaff.Bottom; }
        }

        /// <summary>
        /// Gets the y-coordinate of the central connector point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the connector's central
        /// point from the top edge of the owner section.
        /// </para>
        /// </value>
        public double Center
        {
            get { return upperStaff.Top + (lowerStaff.Bottom - upperStaff.Top) / 2; }
        }

        /// <summary>
        /// Gets the y-coordinate of the top connector point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The offset (in device independent pixels) of the connector's top
        /// point from the top edge of the owner section.
        /// </para>
        /// </value>
        public double Top
        {
            get { return upperStaff.Top; }
        }

        /// <summary>
        /// Gets the lower connected staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The lower staff of the connector.
        /// </para>
        /// </value>
        public Staff LowerStaff
        {
            get { return lowerStaff; }
        }

        /// <summary>
        /// Gets the upper connected staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The upper staff of the connector.
        /// </para>
        /// </value>
        public Staff UpperStaff
        {
            get { return upperStaff; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="LowerStaff"/> property.
        /// </summary>
        private Staff lowerStaff;
        /// <summary>
        /// Holds the value of the <see cref="UpperStaff"/> property.
        /// </summary>
        private Staff upperStaff;
        
        #endregion
    }
}
